/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou Yi Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/
 * @date:        2019/5/20 20:57
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用，转发请注明出处.
 */
package com.example.common.spring.util;

import com.example.common.spring.context.CapAppContext;
import com.google.common.base.Strings;
import org.apache.commons.collections4.MapUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Map;

/**
 * <p>
 *
 * </p>
 *
 * @author Yi
 * @version v1.0.0
 * @date 2019-07-02 22:04
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人} 2019-07-02
 * @modify reason: {方法名}:{原因}
 * ...
 */
public class RestClient {

    private static RestTemplate template;

    private RestClient() {
        template = CapAppContext.getBean("capRestTemplate", RestTemplate.class);
    }

    private static class SingletonHolder{
        private static final RestClient INSTANCE = new RestClient();
    }

    public static RestClient getInstance(){
        return SingletonHolder.INSTANCE;
    }

    /**
     * 执行请求
     *
     * @param url          请求地址
     * @param method       请求方式
     * @param responseType 返回的数据类型
     * @param uriVariables url自动匹配替换的参数，如url为api/{a}/{b},参数为["1","2"],则解析的url为api/1/2，使用Map参数时，遵循按key匹配
     * @return 结果对象
     * @throws RestClientException RestClient异常，包含状态码和非200的返回内容
     */
    public <T> T exchange(String url, HttpMethod method, Class<T> responseType, Object... uriVariables) throws RestClientException {
        return exchange(url, method, null, null, responseType, uriVariables);
    }

    /**
     * 执行请求
     *
     * @param url          请求地址
     * @param method       请求方式
     * @param headers      设置的头信息
     * @param responseType 返回的数据类型
     * @param uriVariables url自动匹配替换的参数，如url为api/{a}/{b},参数为["1","2"],则解析的url为api/1/2，使用Map参数时，遵循按key匹配
     * @return 结果对象
     * @throws RestClientException RestClient异常，包含状态码和非200的返回内容
     */
    public <T> T exchange(String url, HttpMethod method, HttpHeaders headers, Class<T> responseType, Object... uriVariables) throws RestClientException {
        return exchange(url, method, headers, null, responseType, uriVariables);
    }

    /**
     * 执行请求
     *
     * @param url          请求地址
     * @param method       请求方式
     * @param body         要提交的数据
     * @param responseType 返回数据类型
     *                     返回bean时指定Class
     * @param uriVariables url自动匹配替换的参数，如url为api/{a}/{b},参数为["1","2"],则解析的url为api/1/2，使用Map参数时，遵循按key匹配
     * @return 结果对象
     * @throws RestClientException RestClient异常，包含状态码和非200的返回内容
     */
    public  <T> T exchange(String url, HttpMethod method, Object body, Class<T> responseType, Object... uriVariables) throws RestClientException {
        return exchange(url, method, null, body, responseType, uriVariables);
    }

    /**
     * 执行请求
     *
     * @param url          请求地址
     * @param method       请求方式
     * @param httpHeaders  请求头
     * @param body         要提交的数据
     * @param responseType 返回数据类型
     *                     返回bean时指定Class
     * @param uriVariables url自动匹配替换的参数，如url为api/{a}/{b},参数为["1","2"],则解析的url为api/1/2，使用Map参数时，遵循按key匹配
     * @return 结果对象
     * @throws RestClientException RestClient异常，包含状态码和非200的返回内容
     */
    public <T> T exchange(String url, HttpMethod method, HttpHeaders httpHeaders, Object body, Class<T> responseType, Object... uriVariables) throws RestClientException {
        try {
            HttpEntity<?> requestEntity = new HttpEntity(body, httpHeaders);
            requestEntity = convert(requestEntity);

            if (uriVariables.length == 1 && uriVariables[0] instanceof Map) {
                Map<String, ?> _uriVariables = (Map<String, ?>) uriVariables[0];
                return getClient().exchange(url, method, requestEntity, responseType, _uriVariables).getBody();
            }

            return getClient().exchange(url, method, requestEntity, responseType, uriVariables).getBody();
        } catch (Exception e) {
            throw new RestClientException("request failed catch exception!");
        }
    }

    /**
     * 执行请求
     *
     * @param url          请求地址
     * @param method       请求方式
     * @param responseType 返回的数据类型，例：new ParameterizedTypeReference<List<Bean>>(){}
     * @param uriVariables url自动匹配替换的参数，如url为api/{a}/{b},参数为["1","2"],则解析的url为api/1/2，使用Map参数时，遵循按key匹配
     * @return 结果对象
     * @throws RestClientException RestClient异常，包含状态码和非200的返回内容
     */
    public  <T> T exchange(String url, HttpMethod method, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
        return exchange(url, method, null, null, responseType, uriVariables);
    }

    /**
     * 执行请求
     *
     * @param url          请求地址
     * @param method       请求方式
     * @param headers      设置的头信息
     * @param responseType 返回的数据类型，例：new ParameterizedTypeReference<List<Bean>>(){}
     * @param uriVariables url自动匹配替换的参数，如url为api/{a}/{b},参数为["1","2"],则解析的url为api/1/2，使用Map参数时，遵循按key匹配
     * @return 结果对象
     * @throws RestClientException RestClient异常，包含状态码和非200的返回内容
     */
    public  <T> T exchange(String url, HttpMethod method, HttpHeaders headers, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
        return exchange(url, method, headers, null, responseType, uriVariables);
    }

    /**
     * 执行请求
     *
     * @param url          请求地址
     * @param method       请求方式
     * @param body         要提交的数据
     * @param responseType 返回数据类型，例：new ParameterizedTypeReference<List<Bean>>(){}
     *                     返回bean时指定Class
     * @param uriVariables url自动匹配替换的参数，如url为api/{a}/{b},参数为["1","2"],则解析的url为api/1/2，使用Map参数时，遵循按key匹配
     * @return 结果对象
     * @throws RestClientException RestClient异常，包含状态码和非200的返回内容
     */
    public  <T> T exchange(String url, HttpMethod method, Object body, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
        return exchange(url, method, null, body, responseType, uriVariables);
    }

    /**
     * 执行请求
     *
     * @param url          请求地址
     * @param method       请求方式
     * @param httpHeaders  请求头
     * @param body         要提交的数据
     * @param responseType 返回数据类型，例：new ParameterizedTypeReference<List<Bean>>(){}
     *                     返回bean时指定Class
     * @param uriVariables url自动匹配替换的参数，如url为api/{a}/{b},参数为["1","2"],则解析的url为api/1/2，使用Map参数时，遵循按key匹配
     * @return 结果对象
     * @throws RestClientException RestClient异常，包含状态码和非200的返回内容
     */
    public  <T> T exchange(String url, HttpMethod method, HttpHeaders httpHeaders, Object body, ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
        try {
            HttpEntity<?> requestEntity = new HttpEntity(body, httpHeaders);
            requestEntity = convert(requestEntity);

            if (uriVariables.length == 1 && uriVariables[0] instanceof Map) {
                Map<String, ?> _uriVariables = (Map<String, ?>) uriVariables[0];
                return getClient().exchange(url, method, requestEntity, responseType, _uriVariables).getBody();
            }

            return getClient().exchange(url, method, requestEntity, responseType, uriVariables).getBody();
        } catch (Exception e) {
            throw new RestClientException("request failed catch exception!");
        }
    }

    /**
     * 获取一个application/x-www-form-urlencoded头
     *
     * @return
     */
    public  HttpHeaders buildBasicFORMHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        return headers;
    }

    /**
     * 获取一个application/json头
     *
     * @return
     */
    public static HttpHeaders buildBasicJSONHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        return headers;
    }

    /**
     * 获取一个text/html头
     *
     * @return
     */
    public  HttpHeaders buildBasicHTMLHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.TEXT_HTML);
        return headers;
    }

    /**
     * 构建一个json头
     *
     * @param arrays
     * @return
     */
    public  HttpHeaders buildJSONHeaders(Object... arrays) {
        if (arrays.length % 2 != 0) {
            throw new RuntimeException("arrays 长度 必须为偶数");
        }

        HttpHeaders headers = buildBasicJSONHeaders();

        for (int i = 0; i < arrays.length; i++) {
            headers.add(Strings.nullToEmpty(String.valueOf(arrays[i])), Strings.nullToEmpty(String.valueOf(arrays[++i])));
        }

        return headers;
    }

    /**
     * 对bean对象转表单模型做处理
     *
     * @param requestEntity
     * @return
     */
    private  HttpEntity<?> convert(HttpEntity<?> requestEntity) {
        Object body = requestEntity.getBody();
        HttpHeaders headers = requestEntity.getHeaders();

        if (body == null) {
            return requestEntity;
        }

        if (body instanceof Map) {
            MultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>();
            Map<String, ?> _body = (Map<String, ?>) body;
            for (String key : _body.keySet()) {
                multiValueMap.add(key, MapUtils.getString(_body, key));
            }

            requestEntity = new HttpEntity<>(multiValueMap, headers);
        }

        if (headers == null || !MediaType.APPLICATION_FORM_URLENCODED.equals(headers.getContentType())) {
            return requestEntity;
        }

        if (body instanceof String) {
            return requestEntity;
        }

        if (body instanceof Collection) {
            return requestEntity;
        }

        if (body instanceof Map) {
            return requestEntity;
        }

        MultiValueMap<String, Object> formEntity = new LinkedMultiValueMap<>();

        Field[] fields = body.getClass().getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            String name = fields[i].getName();
            String value = null;

            try {
                value = (String) fields[i].get(body);
            } catch (Exception e) {
                e.printStackTrace();
            }

            formEntity.add(name, value);
        }

        return new HttpEntity<>(formEntity, headers);
    }


    /**
     * 请求资源代理
     * - 支持请求文件流
     * @param url
     * @param method
     * @param requestEntity
     * @param responseType
     * @param uriVariables
     * @param <T>
     * @return
     * @throws RestClientException
     */
    public <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)
            throws RestClientException {
        return getClient().exchange(url,method,requestEntity,responseType,uriVariables);
    }

    private  RestTemplate getClient() {
        return template;
    }
}
